/*
 * Licensed to the 南京汇海交通科技有限公司
 *
 * Copyright (c) 2025.
 * All rights reserved.
 *
 * 本软件属南京汇海交通科技有限公司所有，在未获得南京汇海交通科技有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.chframework.boot.web.autoconfigure;

import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.chframework.boot.web.advice.HttpAspect;
import com.chframework.boot.web.exception.error.GlobalErrorAttributes;
import com.chframework.boot.web.exception.error.SpringWebFluxErrorHandler;
import com.chframework.boot.web.reactor.FluxPathController;
import com.chframework.boot.web.reactor.WebFluxFilter;
import com.chframework.monitor.task.MonitorTask;
import com.chframework.common.web.properties.FluxFilterProperty;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext;
import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.CacheControl;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.ResourceHandlerRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.reactive.result.view.ViewResolver;

import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 官方文档
 * https://docs.spring.io/spring-framework/docs/5.2.x/spring-framework-reference/web-reactive.html#webflux
 */
@EnableWebFlux
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(ErrorWebFluxAutoConfiguration.class)
public class SpringWebFluxAutoConfiguration implements WebFluxConfigurer {

    /**
     * 加载配置文件
     * 注意，上面添加了 注解@EnableWebFlux 就要保证 WebFluxProperties对象一定要被注入Spring容器
     */
    @Bean
    @ConditionalOnMissingBean
    @ConfigurationProperties(prefix = "spring.webflux")
    public WebFluxProperties webFluxProperties() {
        return new WebFluxProperties();
    }

    /**
     * 配置 jackson序列化 和 反序列化
     * 注意，上面添加了 注解@EnableWebFlux，然后此处的设置终于生效，否则这里的设置无效。此乃大坑啊。。。
     */
    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper));
        configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper));
    }

    /**
     * 静态资源映射
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**")
                .addResourceLocations("classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/")
                .setCacheControl(CacheControl.maxAge(DateUtil.lengthOfYear(DateUtil.year(new Date())), TimeUnit.DAYS));
    }

    private ApplicationContext applicationContext;
    private MonitorTask monitorTask;
    private ObjectMapper objectMapper;

    @Autowired
    public SpringWebFluxAutoConfiguration(ReactiveWebServerApplicationContext applicationContext, MonitorTask monitorTask,
                                          @Qualifier("objectMapper") ObjectMapper objectMapper) {
        this.applicationContext = applicationContext;
        this.monitorTask = monitorTask;
        this.objectMapper = objectMapper;
    }

    /**
     * 自定义 webflux filter 配置参数
     */
    @Bean
    @ConfigurationProperties(prefix = "application.flux.filter")
    public FluxFilterProperty fluxFilterProperty() {
        return new FluxFilterProperty();
    }

    /**
     * spring-webflux环境下的filter过滤器
     */
    @Bean("webFluxFilter")
    @ConditionalOnProperty(prefix = "application.flux.filter", name = {"enable-web-flux-filter"}, havingValue = "true", matchIfMissing = true)
    public WebFluxFilter webFluxFilter() {
        return new WebFluxFilter();
    }

    /**
     * 搜集api-path列表
     */
    @Bean
    public FluxPathController fluxPathController(RequestMappingHandlerMapping requestMappingHandlerMapping) {
        return new FluxPathController(requestMappingHandlerMapping);
    }

    /**
     * aop 日志切面
     */
    @Bean
    public HttpAspect httpAspect() {
        return new HttpAspect(applicationContext);
    }

    /**
     * spring-webflux全局异常处理 - 自定义错误属性
     */
    @Bean
    public DefaultErrorAttributes errorAttributes() {
        return new GlobalErrorAttributes();
    }

    /**
     * WebFlux错误页 处理程序
     * 自定义路由失败处理。当请求路由地址不匹配或断言为 false 时，Gateway 会默认返回 Whitelabel Error Page 错误页面，这种错误提示不符合我们业务需求。
     * <p>
     * 注意，当前配置类需要赶在 ErrorWebFluxAutoConfiguration 之前创建 ErrorWebExceptionHandler 类型的bean对象。
     * 而 DefaultErrorHandler 是间接成为 ErrorWebExceptionHandler 的子对象的。
     * 此处，必须要翻源码才能实现正确的效果。
     */
    @Bean("defaultErrorWebExceptionHandler")
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler defaultErrorWebExceptionHandler(ErrorAttributes errorAttributes,
                                                                    ServerProperties serverProperties,
                                                                    WebProperties webProperties,
                                                                    ObjectProvider<ViewResolver> viewResolvers,
                                                                    ServerCodecConfigurer serverCodecConfigurer) {
        Resources resourceProperties = webProperties.getResources();
        SpringWebFluxErrorHandler exceptionHandler = new SpringWebFluxErrorHandler(errorAttributes, resourceProperties,
                serverProperties.getError(), applicationContext);
        exceptionHandler.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));
        exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());

        return exceptionHandler;
    }

    /**
     * Configuration of scheduled job for monitoring server's resources.
     */
    @EnableScheduling
    @Configuration(proxyBeanMethods = false)
    class ServerMonitorConfiguration implements SchedulingConfigurer {
        @Override
        public void configureTasks(@NonNull ScheduledTaskRegistrar taskRegistrar) {
            taskRegistrar.addCronTask(monitorTask, "0 * * * * *");
        }
    }

}
